package json5

import (
	"bufio"
	"fmt"
	"io"
	"strings"
)

// Разделитель строк
var NewLine string = "\n"

// Отступ для уровней элементов
var Indent string = "  "

// Запись элементов формата JSON.
// Принимает:
// - []Member      - объект JSON - массив пар "имя: элемент";
// - []interface{} - массив JSON - массив значений или объектов JSON;
// - значение      - строка, число, литерал (true/false/nil).
func PrintJson(json interface{}, w io.Writer) error {
	bufw := bufio.NewWriter(w)
	err := printElement(json, bufw, 0)
	if err != nil {
		return err
	}
	_, err = bufw.WriteString(NewLine)
	if err != nil {
		return err
	}
	return bufw.Flush()
}

func printElement(element interface{}, bufw *bufio.Writer, level int) error {
	switch elm := element.(type) {
	case []Member:
		return printObject(elm, bufw, level+1)
	case []interface{}:
		return printArray(elm, bufw, level+1)
	case string:
		return printString(elm, bufw)
	case nil:
		_, err := bufw.WriteString("null")
		return err
	default:
		_, err := bufw.WriteString(fmt.Sprintf("%v", element))
		return err
	}
}

func printObject(object []Member, bufw *bufio.Writer, level int) error {
	_, err := bufw.WriteString("{")
	if err != nil {
		return err
	}
	if len(object) > 0 {
		_, err := bufw.WriteString(NewLine)
		if err != nil {
			return err
		}
	}
	for n, member := range object {
		_, err := bufw.WriteString(strings.Repeat(Indent, level))
		if err != nil {
			return err
		}
		err = printString(member.Name, bufw)
		if err != nil {
			return err
		}
		_, err = bufw.WriteString(": ")
		if err != nil {
			return err
		}
		err = printElement(member.Element, bufw, level)
		if err != nil {
			return err
		}
		if n < len(object)-1 {
			_, err = bufw.WriteString(",")
			if err != nil {
				return err
			}
		}
		_, err = bufw.WriteString(NewLine)
		if err != nil {
			return err
		}
	}
	if len(object) > 0 {
		_, err = bufw.WriteString(strings.Repeat(Indent, level-1))
		if err != nil {
			return err
		}
	}
	_, err = bufw.WriteString("}")
	if err != nil {
		return err
	}
	return nil
}

func printArray(array []interface{}, bufw *bufio.Writer, level int) error {
	_, err := bufw.WriteString("[")
	if err != nil {
		return err
	}
	if len(array) > 0 {
		_, err := bufw.WriteString(NewLine)
		if err != nil {
			return err
		}
	}
	for n, element := range array {
		_, err := bufw.WriteString(strings.Repeat(Indent, level))
		if err != nil {
			return err
		}
		err = printElement(element, bufw, level)
		if err != nil {
			return err
		}
		if n < len(array)-1 {
			_, err = bufw.WriteString(",")
			if err != nil {
				return err
			}
		}
		_, err = bufw.WriteString(NewLine)
		if err != nil {
			return err
		}
	}
	if len(array) > 0 {
		_, err = bufw.WriteString(strings.Repeat(Indent, level-1))
		if err != nil {
			return err
		}
	}
	_, err = bufw.WriteString("]")
	if err != nil {
		return err
	}
	return nil
}

var eschars = "'\"\\\b\f\n\r\t\v" + string(byte(0)) + ",{}[]"
var replacer = strings.NewReplacer(
	"'", `\'`,
	"\"", `\"`,
	"\\", `\\`,
	"\b", `\b`,
	"\f", `\f`,
	"\n", `\n`,
	"\r", `\r`,
	"\t", `\t`,
	"\v", `\v`,
	string(byte(0)), `\0`)

func printString(s string, bufw *bufio.Writer) error {
	s = replacer.Replace(s)
	if strings.ContainsAny(s, eschars) {
		s = "\"" + s + "\""
	}
	_, err := bufw.WriteString(s)
	return err
}
